package com.order.manager.Model;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * 用户信息实体类
 * 对应数据库表：user_info
 * 1. Serializable 接口
 * 所属技术
 * Java 核心 API（java.io.Serializable），是 Java 序列化机制的基础。
 * 序列化：数据-字符化-内存存储
 * 反序列化：内存存储-反字符化=数据
 * 2. UserDetails 接口
 * 所属技术
 * Spring Security 框架（org.springframework.security.core.userdetails.UserDetails）
 * ，用于封装用户认证和授权信息。
 */
@TableName(value ="user_info")
@Data
public class UserInfo implements Serializable, UserDetails {

    /**
     * 用户唯一标识（主键）
     */
    @TableId
    private String userId;
    /**
     * 登录用户名（唯一）
     */
    private String username;

    /**
     * 用户显示名称
     */
    private String name;

    /**
     * 加密后的登录密码
     */
    private String password;

    /**
     * 用户角色（ROLE_USER、ROLE_ADMIN 等）
     */
    private String role;

    /**
     * 用户状态（1=启用，0=禁用）
     */
    private Integer isEnable;

    /**
     * 账号创建时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    private Date inputTime;

    /**
     * 信息更新时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    private Date updateTime;

    /**
     * 账号删除状态（1=正常，0=已删除）
     */
    private Integer state;

    /**
     * 用户联系地址
     */
    private String address;

    /**
     * 用户联系电话
     */
    private String phone;

    /**
     * 用户性别
     */
    private String sex;

    /**
     * 用户头像URL
     */
    private String img;

    /**
     * 用户账户余额
     */
    private String money;

    /**
     * 非数据库字段：序列化版本号
     * //    @TableField(exist = false) 是 MyBatis-Plus 框架提供的注解，
     * //    它的作用是告诉框架：这个字段在数据库对应的表中并不存在。
     * //    在进行数据库操作（像插入、查询）时，框架会自动忽略被该注解标记的字段。
     * //    序列化标识：在对象实现 Serializable 接口时，serialVersionUID 能用来保证序列化和反序列化过程的版本一致性。
     * //    保持兼容性：如果没有显式定义这个 ID，Java 会根据类的结构自动生成。
     * //     不过，当类发生改变时，自动生成的 ID 可能会变化，进而导致反序列化失败。
     * //    在 Java 编程里，序列化（Serialization） 是一种把对象的状态转换为字节流的机制。通过这个机制，
     * //    对象可以被存储到文件中、在网络上进行传输，或者在内存里缓存起来。
     * //    而 反序列化（Deserialization） 则是相反的过程，它能将字节流重新恢复成对象。
     */
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    // ------------------------- UserDetails 接口实现 -------------------------

    /**
     * 获取用户权限集合
     * 将数据库中的 role 字段转换为 Spring Security 权限格式
     * 例如：role="ROLE_ADMIN" 会被转换为 GrantedAuthority 对象
     */
    @Override

    /**
     *    //其元素类型是 GrantedAuthority 或它的某个子类。以
     * List.of
     *不可变列表意味着列表创建后不能添加、删除或修改元素，保证了安全性。
     * SimpleGrantedAuthority封装权限值
     * 也就是返回一个经过安全权限认证封装的不可变列表
     * ke列
     * 一个
     * grandti
     * asorrywoti
     */
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority(role));
    }

    /**
     * 判断账户是否未过期
     * 固定返回 true，表示账户永不过期
     * 如需实现账户过期逻辑，可修改此处
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 判断账户是否未锁定
     * 根据 isEnable 字段判断：
     * 1 - 账户未锁定（可用）
     * 0 - 账户已锁定（不可用）
     */
    @Override
    public boolean isAccountNonLocked() {
        return isEnable == 1;
    }

    /**
     * 判断凭证（密码）是否未过期
     * 固定返回 true，表示密码永不过期
     * 如需实现密码过期逻辑，可修改此处
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 判断账户是否启用
     * 根据 isEnable 字段判断：
     * 1 - 账户启用
     * 0 - 账户禁用
     */
    @Override
    public boolean isEnabled() {
        return isEnable == 1;
    }

    /**
     * 获取登录用户名
     * 实现 UserDetails 接口的方法，返回用户名用于认证
     */
    @Override
    public String getUsername() {
        return username;
    }

    /**
     * 获取加密密码
     * 实现 UserDetails 接口的方法，返回加密后的密码用于认证
     */
    @Override
    public String getPassword() {
        return password;
    }

    // ------------------------- 重写 Object 方法 -------------------------

    /**
     * 对象相等性比较
     * 比较当前对象与另一个对象的所有字段是否相等
     *userId.equals(userInfo.userId);这个是userId与userInfo.userId进行比较
     * 保持数据的一致性Hibernate、MyBatis 在缓存和持久化时，通过 equals() 判断对象状态是否变化
     */
    //在 Java 中，this 是一个引用变量，它指向当前对象（即调用当前方法的对象实例）
    //@Override 是 Java 中的一个编译器注解，其核心作用是：
    //显式声明当前方法是重写（Override）父类或接口的方法，并让编译器进行以下检查：
    @Override
    public boolean equals(Object that) {
        //检查两个对象是否为同一个实例
        if (this == that) {
            return true;
        }
        //当前对象（this）不可能为 null（否则无法调用方法），因此若 that 为 null，直接返回 false
        if (that == null) {
            return false;
        }
        //确保 that 是 UserInfo 类型（也就是确保我的数值是我这个类的
        if (getClass() != that.getClass()) {
            return false;
        }
        //将 Object 类型的参数 that 强制转换为 UserInfo 类型，以便访问 UserInfo 类特有的字段和方法。
//        先判断第一个值是否为空，如果为空就判断第二个值是否为空，否则比较两个值
        UserInfo other = (UserInfo) that;
        return (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))

                && (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername()))
                && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
                && (this.getPassword() == null ? other.getPassword() == null : this.getPassword().equals(other.getPassword()))
                && (this.getRole() == null ? other.getRole() == null : this.getRole().equals(other.getRole()))
                && (this.getIsEnable() == null ? other.getIsEnable() == null : this.getIsEnable().equals(other.getIsEnable()))
                && (this.getInputTime() == null ? other.getInputTime() == null : this.getInputTime().equals(other.getInputTime()))
                && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()))
                && (this.getState() == null ? other.getState() == null : this.getState().equals(other.getState()))
                && (this.getAddress() == null ? other.getAddress() == null : this.getAddress().equals(other.getAddress()))
                && (this.getPhone() == null ? other.getPhone() == null : this.getPhone().equals(other.getPhone()))
                && (this.getSex() == null ? other.getSex() == null : this.getSex().equals(other.getSex()))
                && (this.getImg() == null ? other.getImg() == null : this.getImg().equals(other.getImg()))
                && (this.getMoney() == null ? other.getMoney() == null : this.getMoney().equals(other.getMoney()));
    }

    /**
     * 生成对象哈希码
     * 基于所有字段计算哈希值，确保相等对象具有相同哈希码
     * hashCode() 其核心作用是：
     * 1.为对象生成一个 整数哈希码，用于在哈希表（如 HashMap、HashSet）中快速查找和存储对象。
     * 2.若两个对象通过 equals() 比较相等，则它们的 hashCode() 必须相同
     * 比如equals()数据1和数据2相同为2，则套用同一个哈希值公式计算结果也相同，
     * 如果equals()与hashCode() 不相同则会导致哈希表无法正常工作
     * 也就是说，我返回了对应的哈希值，就可以把对应的数据存储到对应的空间，
     * 如果u1和u2相同就存到对应的空间，如果不同就分别存放
     *
     * 在HashMap、HashSet 中正确存储和查找
     * 1.HashMap是Java中最常用的键值对存储容器，基于哈希表（Hash Table）实现，
     * 不允许存储重复的键（若插入相同键，新值会覆盖旧值）
     * // u2 会覆盖 u1，因键被视为相同
     * 当我u1的值被u2的值覆盖，但是我要获取u1的值，因为两者的值一样所以我仍然获取到了对的值
     * 2.HashSet是Java中基于哈希表实现的集合不允许存储重复元素（通过 equals() 判断）
     * // u2 不会被添加，因与 u1 视为相同元素
     * 无序（不保证元素的插入顺序或访问顺序）
     * 允许存储 null 元素（但只能有一个）
     *
     *相对于两条不同的数据而言，因为result会不断的根据上一条公式而改变
     */
    @Override
    public int hashCode() {
        // 选择 31 作为乘数，可减少哈希冲突
        //质数能减少哈希冲突的概率
        final int prime = 31;
        // 初始值
        int result = 1;
        //判断getUserId() == null是否为null，为null，reault值为0，否则调用getUserId().hashCode()
        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
        result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode());
        result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
        result = prime * result + ((getPassword() == null) ? 0 : getPassword().hashCode());
        result = prime * result + ((getRole() == null) ? 0 : getRole().hashCode());
        result = prime * result + ((getIsEnable() == null) ? 0 : getIsEnable().hashCode());
        result = prime * result + ((getInputTime() == null) ? 0 : getInputTime().hashCode());
        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
        result = prime * result + ((getState() == null) ? 0 : getState().hashCode());
        result = prime * result + ((getAddress() == null) ? 0 : getAddress().hashCode());
        result = prime * result + ((getPhone() == null) ? 0 : getPhone().hashCode());
        result = prime * result + ((getSex() == null) ? 0 : getSex().hashCode());
        result = prime * result + ((getImg() == null) ? 0 : getImg().hashCode());
        result = prime * result + ((getMoney() == null) ? 0 : getMoney().hashCode());
        return result;
    }

    /**
     * 对象字符串表示
     * 返回包含所有字段值的字符串，用于调试和日志记录
     */
    @Override
    public String toString() {
        //StringBuilder 是 Java 中用于高效拼接字符串的类,这个新声明调用就是用于进行以下字符拼接
        //在循环拼接、复杂字符串构建场景中，始终使用 StringBuilder
        StringBuilder sb = new StringBuilder();
        //获取当前对象的类名（不含包路径），并追加到 StringBuilder 中。
        //System.out.println(user.getClass().getSimpleName());  // 输出 "UserInfo"
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", userId=").append(userId);
        sb.append(", username=").append(username);
        sb.append(", name=").append(name);
        sb.append(", password=").append(password);
        sb.append(", role=").append(role);
        sb.append(", isEnable=").append(isEnable);
        sb.append(", inputTime=").append(inputTime);
        sb.append(", updateTime=").append(updateTime);
        sb.append(", state=").append(state);
        sb.append(", address=").append(address);
        sb.append(", phone=").append(phone);
        sb.append(", sex=").append(sex);
        sb.append(", img=").append(img);
        sb.append(", money=").append(money);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}